#include "predictor.h"

//TableType t;
// define local, global and choice tables
uint8_t  local_table[L_TABLE_SIZE];
uint8_t global_table[G_C_TABLE_SIZE];
uint8_t choice_table[G_C_TABLE_SIZE];

// define path history of 12 bit int for upto 12 branches
uint path_history;

    bool PREDICTOR::get_prediction(const branch_record_c* br, const op_state_c* os)
    {
		/* replace this code with your own */
            bool prediction = false;
            bool choice_prediction = false;

		/*	printf("%0x %0x %1d %1d %1d %1d ",br->instruction_addr,
			                                br->branch_target,br->is_indirect,br->is_conditional,
											br->is_call,br->is_return);
		*/

//            	if (!(br->is_conditional) || (br->is_call) || (br->is_return))
            	if (!(br->is_conditional))
            	{
	        		prediction = true;
            	}
		else
		{

		choice_prediction = PREDICTOR::get_choice_prediction(br, os);

		//get_choice_prediction returns TRUE for global prediction

		if(choice_prediction)
			prediction = PREDICTOR::get_global_prediction(br, os);
		else
			prediction = PREDICTOR::get_local_prediction(br, os);

	    }
		return prediction;   // true for taken, false for not taken
        }


    // Update the predictor after a prediction has been made.  This should accept
    // the branch record (br) and architectural state (os), as well as a third
    // argument (taken) indicating whether or not the branch was taken.
    void PREDICTOR::update_predictor(const branch_record_c* br, const op_state_c* os, bool taken)
        {
		/* replace this code with your own */
			//printf("%1d\n",taken);
    		PREDICTOR::update_choice_predictor(br, os, taken);
    		PREDICTOR::update_global_predictor(br, os, taken);
    		PREDICTOR::update_local_predictor(br, os, taken);
    		PREDICTOR::update_path_history(taken);
        }

	bool PREDICTOR::get_global_prediction(const branch_record_c* br, const op_state_c* os)
	{
		bool prediction;
		uint8_t global_predictor = read_table(Global, path_history);
		if(global_predictor >= G_COUNTER_NOT_TAKEN_STATES)
			prediction = true;
		else
			prediction = false;

		return prediction;
	}


	bool PREDICTOR::get_local_prediction(const branch_record_c* br, const op_state_c* os)
	{
	bool prediction;
	uint PC_10bit;
	uint8_t local_table_entry;

	//Storing a local copy of the 10 bits of PC
	PC_10bit = get_PC_10bits(br);

	//Use the 10 bits of PC to index the local table
	local_table_entry = read_table(Local, PC_10bit);

	//L_COUNTER_NOT_TAKEN_STATES represents the number of states out of 8 that are interpreted for 'not taken'
	if(local_table_entry >= L_COUNTER_NOT_TAKEN_STATES)
	{
		prediction = true;
	}
	else
	{
		prediction = false;
	}

	return prediction;

	}

	uint PREDICTOR::get_PC_10bits(const branch_record_c* br)
	{

		//Using the [11:2] bits of the PC
		uint PC_10bits;
		PC_10bits = (br -> instruction_addr);
	      	PC_10bits >>= SHIFT_AMOUNT;
		PC_10bits &=  MASK_PC;
//              PC_10bits = (br-> instruction_addr) & 0x000003FF;
//		PC_10bits >>= 1;
		return PC_10bits;
	}

    	bool PREDICTOR::get_choice_prediction(const branch_record_c* br, const op_state_c* os)
	{
		bool prediction;
		uint8_t choice_predictor = read_table(Choice, path_history);
		if(choice_predictor >= C_COUNTER_NOT_TAKEN_STATES)
			prediction = true;
		else
			prediction = false;

		return prediction;
	}

    	void PREDICTOR::update_path_history(bool actual_taken)
	{
		// flush oldest branch history denoted by LSB
		path_history >>= 1;
		// update latest branch history if branch was actually taken
		if(actual_taken)
			path_history |= (1 << (HISTORY_SIZE - 1));
	}

    	void PREDICTOR::update_global_predictor(const branch_record_c* br, const op_state_c* os, bool actual_taken)
	{
		uint8_t old_value = read_table(Global, path_history);
		uint8_t new_value = evaluate_updation_value(Global, old_value, actual_taken, false, false);
		write_table(Global, path_history, new_value);
	}

	void PREDICTOR::update_local_predictor(const branch_record_c* br, const op_state_c* os, bool actual_taken)
	{
		uint8_t old_value = read_table(Local, get_PC_10bits(br));
		uint8_t new_value = evaluate_updation_value(Local, old_value, actual_taken, false, false);
		write_table(Local, get_PC_10bits(br), new_value);
	}


    	uint8_t PREDICTOR::read_table(TableType type, uint32_t index)
	{
		if(type == Local) {
			return local_table[index];
		} else if(type == Global) {
			return global_table[index];
		} else {
			return choice_table[index];
		}
	}


    	void PREDICTOR::write_table(TableType type, uint32_t index, uint8_t value)
	{
		if(type == Local) {
			local_table[index] = value;
		} else if(type == Global) {
			global_table[index] = value;
		} else {
			choice_table[index] = value;
		}
	}


    	uint8_t PREDICTOR::evaluate_updation_value(TableType type, uint8_t value, bool actual_taken, bool global_p, bool local_p)
	{
		uint8_t temp_value = value;
                if(type == Choice){
                    if(global_p != local_p){
                       if(local_p == actual_taken)
	                        temp_value -= (temp_value !=0);
                       else
			temp_value += (value != (C_COUNTER_STATES - 1));
                       }
                       }
                else{

		if(actual_taken) {
			if(type == Local) {
				temp_value += (value != (L_COUNTER_STATES - 1));
			} else if(type == Global) {
				temp_value += (value != (G_COUNTER_STATES - 1));
			}

		} else {
			if(type == Local) {
				temp_value -= (value != 0);
			} else if(type == Global) {
				temp_value -= (value != 0);
			}

		}
            }
		return temp_value;
	}



    	void PREDICTOR::update_choice_predictor(const branch_record_c* br, const op_state_c* os, bool actual_taken)
	{       bool local_prediction, global_prediction;
                local_prediction = get_local_prediction(br,os);
                global_prediction = get_global_prediction(br,os);
		uint8_t old_value = read_table(Choice, path_history);
		uint8_t new_value = evaluate_updation_value(Choice,old_value, actual_taken,global_prediction,local_prediction);
		write_table(Choice, path_history, new_value);

	}


	void PREDICTOR :: init_counters()
	{
		uint8_t i;

//-------The commented code to be used when initializing the table in non-uniform way------------
//------Not used since no much gains were seen in prediction rate------------------------------- 

//		uint32_t block_base;
//		uint32_t cnt;
/*
		for(i = 0; i < (G_COUNTER_STATES - 1); i++)
		{
			block_base = i*(G_C_TABLE_SIZE/G_COUNTER_STATES);
			for(cnt = 0; cnt < ((G_C_TABLE_SIZE/G_COUNTER_STATES) - 1); cnt++)
			{
				global_table[(cnt + block_base)] = G_TABLE_INIT_EXPR;
			}
		}
		path_history = PATH_HISTORY_INIT_VAL;

		for(i = 0; i < (G_COUNTER_STATES - 1); i++)
		{
			block_base = i*(L_TABLE_SIZE/L_COUNTER_STATES);
			for(cnt = 0; cnt < ((L_TABLE_SIZE/L_COUNTER_STATES) - 1); cnt++)
			{
				local_table[(cnt + block_base)] = L_TABLE_INIT_EXPR;
			}
		}
*/

		for(i = 0; i < G_C_TABLE_SIZE; i++)
		{
			global_table[i] = G_TABLE_INIT_EXPR;
			choice_table[i] = C_TABLE_INIT_EXPR;
		}


		for(i = 0; i < L_TABLE_SIZE; i++)
		{
			local_table[i] = L_TABLE_INIT_EXPR;
		}

	}

